#include "http.h"
#include "android.h"
#include "config.h"
#include "hashmap.h"
#include "music.h"
#include <cjson/cJSON.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static int listenfd = -1;
static struct sockaddr_in servaddr;

static config_t *conf;

static void *handlereq(void *arg);
static ssize_t recvline(int connfd, char *buf, size_t maxsize);
static void parsereqline(const char *reqline, size_t size, httpreq_t *req);
static void parsereqbody(const char *body, size_t size, httpreq_t *req);
static void serverok(httpreq_t *req);
// static void unimplemented(httpreq_t *req);
static void cannot_execute(httpreq_t *req);
static void bad_request(httpreq_t *req);

static void *handle_android(void *arg);

void http_init() {
  conf = readconf();
  listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  if (listenfd == -1) {
    char loginfo[32];
    sprintf(loginfo, "[%s, %d] http listen", __FILE__, __LINE__);
    perror(loginfo);
    exit(-1);
  }

  memset(&servaddr, 0, sizeof(struct sockaddr_in));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(conf->http_port);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  int ret;

  ret =
      bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));

  if (ret == -1) {
    perror("http bind");
    close(listenfd);
    exit(-1);
  }

  ret = listen(listenfd, conf->http_maxclientnum);

  if (ret == -1) {
    perror("http listen");
    close(listenfd);
    exit(-1);
  }
}

void http_startup() {
  while (true) {
    httpreq_t *req = malloc(sizeof(httpreq_t));

    req->connfd =
        accept(listenfd, (struct sockaddr *)&req->caddr, &req->addrlen);

    if (req->connfd == -1) {
      char loginfo[32];
      sprintf(loginfo, "[%s, %d] http accept client", __FILE__, __LINE__);
      perror(loginfo);
      free(req);
      continue;
    }

    int ret = pthread_create(&req->tid, NULL, handlereq, req);

    if (ret == -1) {
      fprintf(stderr, "[%s, %d] failed to create http client thread\n",
              __FILE__, __LINE__);
      free(req);
      break;
    }
  }

  close(listenfd);
}

void *handlereq(void *arg) {
  httpreq_t *req = arg;
  char recvbuf[1024];

  ssize_t n = recvline(req->connfd, recvbuf, sizeof(recvbuf));
  parsereqline(recvbuf, n, req);

  if (strcasecmp(req->method, "post") != 0) {
    // skip rest request header
    while (n > 0 && strcmp("\n", recvbuf)) {
      n = recvline(req->connfd, recvbuf, sizeof(recvbuf));
    }

    bad_request(req);
    goto END_REQ;
  }

  n = recvline(req->connfd, recvbuf, sizeof(recvbuf));

  while (n > 0 && strcmp("\n", recvbuf) != 0) {
    char ch = recvbuf[15];
    recvbuf[15] = '\0';

    if (strcasecmp(recvbuf, "Content-Length:") == 0) {
      req->content_len = atoi(&recvbuf[16]);
      recvbuf[15] = ch;
    } else {
      // get content_type
      ch = recvbuf[13];
      if (strcasecmp(recvbuf, "Content-Type:") == 0) {
        strcpy(req->content_type, &recvbuf[14]);
        recvbuf[13] = ch;
      }
    }

    n = recvline(req->connfd, recvbuf, sizeof(recvbuf));
  }

  // get request body
  char *ptr = recvbuf;
  ssize_t i = 0;

  while (i < req->content_len) {
    ssize_t nrecv = recv(req->connfd, ptr, sizeof(recvbuf), 0);

    if (nrecv < 0) {
      if (errno == EINTR) {
        nrecv = 0;
      } else {
        break;
      }
    } else if (nrecv == 0) {
      break;
    }

    i += nrecv;
  }
  recvbuf[i] = '\0';

  parsereqbody(recvbuf, strlen(recvbuf), req);

END_REQ:
  close(req->connfd);
  free(req);
  return NULL;
}

void parsereqline(const char *reqline, size_t size, httpreq_t *req) {
  int i = 0, j = 0;

  /* get request method */
  while (!isspace(reqline[j]) && i < sizeof(req->method) - 1) {
    req->method[i] = tolower(reqline[j]);
    ++i;
    ++j;
  }
  req->method[i] = '\0';

  /* skip space */
  while (isspace(reqline[j]) && j < size) {
    ++j;
  }

  /* get url */
  i = 0;
  while (!isspace(reqline[j]) && i < sizeof(req->url) - 1 && j < size) {
    req->url[i] = reqline[j];
    ++i;
    ++j;
  }
  req->url[i] = '\0';

  /* skip space */
  while (isspace(reqline[j]) && j < size) {
    ++j;
  }

  /* get protocol */
  i = 0;
  while (!isspace(reqline[j]) && i < sizeof(req->protocol) - 1 && j < size) {
    req->protocol[i] = reqline[j];
    ++i;
    ++j;
  }

  req->protocol[i] = '\0';
}

void parsereqbody(const char *body, size_t size, httpreq_t *req) {
  cJSON *root = cJSON_Parse(body);

  if (!root) {
    bad_request(req);
    cJSON_Delete(root);
    return;
  }

  cJSON *device_name_item = cJSON_GetObjectItem(root, "deviceName");
  cJSON *device_id_item = cJSON_GetObjectItem(root, "deviceID");
  cJSON *songlist_item = cJSON_GetObjectItem(root, "songlist");

  if (!device_name_item || !device_id_item || !songlist_item ||
      !cJSON_IsArray(songlist_item)) {
    bad_request(req);
    cJSON_Delete(root);
    return;
  }

  char *device_name = device_name_item->valuestring;
  char *device_id = device_id_item->valuestring;

  int song_num = cJSON_GetArraySize(songlist_item);

  if (song_num == 0) {
    bad_request(req);
    cJSON_Delete(root);
    return;
  }

  musictask_t *task = malloc(sizeof(musictask_t));
  create_musictask(task, song_num);

  strncpy(task->device_id, device_id, sizeof(task->device_id));
  strncpy(task->device_name, device_name, sizeof(task->device_name));

  for (int i = 0; i < song_num; ++i) {
    cJSON *song_item = cJSON_GetArrayItem(songlist_item, i);
    cJSON *number_item = cJSON_GetObjectItem(song_item, "number");
    cJSON *song_name_item = cJSON_GetObjectItem(song_item, "songName");
    cJSON *song_info_item = cJSON_GetObjectItem(song_item, "songInfo");
    cJSON *singer_info_item = cJSON_GetObjectItem(song_item, "singerInfo");

    if (!number_item || !song_name_item || !song_info_item ||
        !singer_info_item || !cJSON_IsNumber(number_item)) {
      bad_request(req);
      cJSON_Delete(root);
      return;
    }

    MusicNode *musicptr = get_musicptr(&task->musicvec, i);

    musicptr->number = number_item->valueint;
    strncpy(musicptr->name, song_name_item->valuestring,
            sizeof(musicptr->name));
    strncpy(musicptr->info, song_info_item->valuestring,
            sizeof(musicptr->info));
    strncpy(musicptr->singer, singer_info_item->valuestring,
            sizeof(musicptr->singer));
  }

  print_musictask(task);

  pthread_t tid;
  int ret = pthread_create(&tid, NULL, handle_android, task);

  if (!ret) {
    cannot_execute(req);
  } else {
    serverok(req);
  }

  cJSON_Delete(root);
}

void *handle_android(void *arg) {
  pthread_detach(pthread_self());
  musictask_t *task = (musictask_t *)arg;
  uint8_t dev_id = strid2uint8(task->device_id);
  int connfd = get_connfd_by_dev_id(dev_id);

  config_t *conf = readconf();

  char *file_path = conf->music_file_path;
  int music_num = task->musicvec.len;
  int path_len = strlen(file_path);

  if (file_path[path_len - 1] != '/') {
    file_path[path_len++] = '/';
    file_path[path_len] = '\0';
  }

  char filename[127] = {0};
  uint8_t *file_sizes = malloc(task->musicvec.len * sizeof(uint8_t));

  for (int i = 0; i < task->musicvec.len; ++i) {
    strncpy(filename, conf->music_file_path, sizeof(filename));
    strncat(filename, file_path, path_len);

    if (access(filename, R_OK) == -1) {
      fprintf(stderr, "[%s, %d] file %s access error.\n", __FILE__, __LINE__,
              filename);
      delete_musictask(task);
      return NULL;
    }

    struct stat statbuf;
    stat(filename, &statbuf);
    file_sizes[i] = statbuf.st_size;
  }

  int total_size;
  char *resp = mkresp2android(Resp2AndroidType.TRANSFER, music_num, file_sizes,
                              &total_size);

  free(file_sizes);

  send_android_msg(connfd, resp, total_size);
  free(resp);
  put_task(dev_id, task);

  return NULL;
}

ssize_t recvline(int connfd, char *buf, size_t maxsize) {
  int i = 0, n;
  char c = 0;

  while (i < maxsize - 1 && c != '\n') {
    n = recv(connfd, &c, 1, 0);

    if (n <= 0) {
      break;
    }

    if (c == '\r') {
      n = recv(connfd, &c, 1, MSG_PEEK);

      if (n > 0 && c == '\n') {
        n = recv(connfd, &c, 1, 0);
      } else {
        c = '\n';
      }
    }

    buf[i++] = c;
  }

  buf[i] = '\0';

  return i;
}

void serverok(httpreq_t *req) {
  char buf[1024];
  sprintf(buf, "HTTP/1.0 200 OK\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "Content-Type: text/plain; charset=utf-8\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "success");
  send(req->connfd, buf, strlen(buf), 0);
}

/*void unimplemented(httpreq_t *req)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "%s\r\n", HTTP_SERVER_STR);
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html; charset=utf-8\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "<html>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "<head>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "<title>Method Not Implemented</title>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "</head>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "<body>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "<p>HTTP request method not supported.</p>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "</body>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
    sprintf(buf, "</html>\r\n");
    send(req->connfd, buf, strlen(buf), 0);
}*/

void cannot_execute(httpreq_t *req) {
  char buf[1024];

  sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "Content-type: text/html; charset=utf-8\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "\r\n");
  send(req->connfd, buf, strlen(buf), 0);
  sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
  send(req->connfd, buf, strlen(buf), 0);
}

void bad_request(httpreq_t *req) {
  char buf[1024];

  sprintf(buf, "HTTP/1.0 400 Bad Request\r\n");
  send(req->connfd, buf, sizeof(buf), 0);
  sprintf(buf, "Content-type: text/html; charset=utf-8\r\n");
  send(req->connfd, buf, sizeof(buf), 0);
  sprintf(buf, "\r\n");
  send(req->connfd, buf, sizeof(buf), 0);
  sprintf(buf, "<p>Your browser sent a bad request</p>\r\n");
  send(req->connfd, buf, sizeof(buf), 0);
}
